package member

import (
	"phant-operator/pkg/apis/phant/v1alpha1"
	"phant-operator/pkg/controller"
	"phant-operator/pkg/manager"

	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/klog/v2"
)

type patroniManager struct {
	deps *controller.Dependencies
}

// NewPatroniManager returns a *patroniManager
func NewPatroniManager(deps *controller.Dependencies) manager.Manager {
	return &patroniManager{
		deps: deps,
	}
}

// Sync objects v1alpha1.PhantCluster in future
func (m *patroniManager) Sync(pg *v1alpha1.PostgresCluster) error {
	err := m.syncRBAC(pg)
	if err != nil {
		return err
	}
	return nil
}

func (m *patroniManager) syncRBAC(pg *v1alpha1.PostgresCluster) error {

	//meta.SetName(name)
	serviceAccountName := pg.Spec.Patroni.ServiceAccountName
	if serviceAccountName == "" {
		serviceAccountName = pg.Spec.ServiceAccountName
	}
	if serviceAccountName == "" {
		// need to add labels for instance clusters
		serviceAccountName = pg.GenerateName
	}
	err := m.syncRole(serviceAccountName, pg)
	if err != nil {
		return err
	}
	klog.V(4).Infof("Sync RBAC ClusterRole %s for PhantCluster: %s/%s successfully", serviceAccountName, pg.GetNamespace(), pg.GetName())

	serviceAccount := &corev1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Name:            serviceAccountName,
			Namespace:       pg.GetNamespace(),
			Labels:          pg.GetLabels(),
			OwnerReferences: []metav1.OwnerReference{controller.GetOwnerRef(pg)},
		},
	}
	serviceAccount.SetNamespace(pg.GetNamespace())
	_, err = m.deps.TypedControl.CreateOrUpdateServiceAccount(pg, serviceAccount)
	if err != nil {
		return controller.RequeueErrorf("error creating or updating pgantCluster serviceaccount: %v", err)
	}
	err = m.syncRoleBinding(serviceAccountName, pg)
	if err != nil {
		return err
	}

	// DCS default as kubernetes need cluster level permisiion
	err = m.syncClusterRole(serviceAccountName, pg)
	if err != nil {
		klog.Error(err)
	}
	err = m.syncClusterRoleBinding(serviceAccountName, pg)
	if err != nil {
		klog.Error(err)
		return err
	}
	klog.Infof("Sync RBAC with ServiceAccount %s for PhantCluster: %s/%s successfully", serviceAccountName, pg.GetNamespace(), pg.GetName())
	return nil

}

func (m *patroniManager) syncRole(name string, pg *v1alpha1.PostgresCluster) error {

	meta := metav1.ObjectMeta{
		Name:            name,
		Namespace:       pg.GetNamespace(),
		Labels:          pg.GetLabels(),
		OwnerReferences: []metav1.OwnerReference{controller.GetOwnerRef(pg)},
	}
	clusterPolicyRule := rbacv1.PolicyRule{
		APIGroups:     []string{v1alpha1.GroupName},
		Resources:     []string{v1alpha1.PostgresClusterName},
		ResourceNames: []string{meta.GetName()},
		Verbs:         []string{"get"},
	}
	// preferIPv6 = cluster.Spec.PreferIPv6
	// Default ServiceAccountName .metadata.name
	// Ensure RBAC
	role := &rbacv1.Role{
		ObjectMeta: meta,
		Rules: []rbacv1.PolicyRule{
			clusterPolicyRule,
			{
				APIGroups: []string{corev1.GroupName},
				Resources: []string{"secrets"},
				Verbs:     []string{"get", "list", "watch"},
			},
			{
				APIGroups: []string{corev1.GroupName},
				Resources: []string{"pods", "endpoints", "configmaps"},
				Verbs:     []string{"get", "list", "watch", "patch", "update"},
			},
		},
	}
	role.SetNamespace(pg.GetNamespace())
	_, err := m.deps.TypedControl.CreateOrUpdateRole(pg, role)
	if err != nil {
		return controller.RequeueErrorf("error creating or updating PhantCluster role: %v", err)
	}
	return nil
}

func (m *patroniManager) syncRoleBinding(name string, pg *v1alpha1.PostgresCluster) error {
	roleBinding := &rbacv1.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,

			Namespace:       pg.GetNamespace(),
			Labels:          pg.GetLabels(),
			OwnerReferences: []metav1.OwnerReference{controller.GetOwnerRef(pg)},
		},
		Subjects: []rbacv1.Subject{{
			Kind: rbacv1.ServiceAccountKind,
			Name: name,
		}},
		RoleRef: rbacv1.RoleRef{
			Kind:     "Role",
			Name:     name,
			APIGroup: rbacv1.GroupName,
		},
	}
	roleBinding.SetNamespace(pg.GetNamespace())
	_, err := m.deps.TypedControl.CreateOrUpdateRoleBinding(pg, roleBinding)
	if err != nil {
		return controller.RequeueErrorf("error creating or updating PhantCluster rolebinding: %v", err)
	}
	return nil
}

func (m *patroniManager) syncClusterRole(name string, pg *v1alpha1.PostgresCluster) error {
	clusterRole := &rbacv1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			// Namespace required is private interface Create mechinism for namespace and cluster scope level
			// It will not be present in cluster level CRD.
			// Namespace:       pg.GetNamespace(),
			Labels: pg.GetLabels(),
			// cluster-scoped resource must not have a namespace-scoped owner, owner's namespace pg-admin, requeuing
			// it dosen't need a namespace-scoped OwnerReferences, same to clusterrolebinding
		},
		Rules: []rbacv1.PolicyRule{
			{
				APIGroups: []string{corev1.GroupName},
				Resources: []string{"endpoints"},
				Verbs:     []string{"get"},
			},
		},
	}
	_, err := m.deps.TypedControl.CreateOrUpdateClusterRole(pg, clusterRole)
	if err != nil {
		return controller.RequeueErrorf("error creating or updating PhantCluster clusterrole: %v", err)
	}
	return nil
}

func (m *patroniManager) syncClusterRoleBinding(name string, pg *v1alpha1.PostgresCluster) error {

	_, err := m.deps.TypedControl.CreateOrUpdateClusterRoleBinding(pg, &rbacv1.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: pg.GetLabels(),
		},
		Subjects: []rbacv1.Subject{{
			Kind:      rbacv1.ServiceAccountKind,
			Name:      name,
			Namespace: pg.Namespace,
		}},
		RoleRef: rbacv1.RoleRef{
			Kind:     "ClusterRole",
			Name:     name,
			APIGroup: rbacv1.GroupName,
		},
	})
	if err != nil {
		return controller.RequeueErrorf("error creating or updating PhantCluster clusterrolebinding: %v", err)
	}
	return nil
}
